/*
* Copyright 2007 Stuart McCulloch
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.citrus.maven.inherit;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaSource;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
* Support mojo inheritance by merging the local plugin metadata with metadata from dependent plugins.
* <p/>
* Mojo inheritance is requested using a custom javadoc tag:
* <p/>
* <code><pre>
* ...
* @extendsPlugin archetype
* @goal create
* ...
* </pre></code>
* <p/>
* By default the current goal is taken as the goal to be extended. To extend a different goal use:
* <p/>
* <code><pre>
* ...
* @extendsPlugin archetype
* @extendsGoal create
* @goal create-project
* ...
* </pre></code>
*
* @goal inherit
* @phase compile
* @requiresDependencyResolution compile
*/
public class InheritMojo extends AbstractMojo {
/** classic mojo tag */
private static final String GOAL = "goal";
/** new inheritance mojo tag */
private static final String EXTENDS_PLUGIN = "extendsPlugin";
/** new inheritance mojo tag */
private static final String EXTENDS_GOAL = "extendsGoal";
/**
* local plugin project
*
* @parameter expression="${project}"
* @readonly
*/
private MavenProject m_project;
/**
* output directory for the plugin project
*
* @parameter expression="${project.build.outputDirectory}"
* @readonly
*/
private File m_outputDirectory;
/**
* support for accessing archives
*
* @component
*/
private ArchiverManager m_archiverManager;
/**
* support for artifact resolution
*
* @parameter default-value="${localRepository}" alias="local.repository"
*/
ArtifactRepository m_localRepository;
/**
* support for artifact resolution
*
* @component
*/
ArtifactResolver m_resolver;
/** Maven plugin entry-point */
public void execute()
throws MojoExecutionException {
// pre-load available plugin metadata - local and dependencies
PluginXml targetPlugin = loadPluginMetadata(m_outputDirectory);
// select maven source directories
JavaDocBuilder builder = new JavaDocBuilder();
for (Iterator i = m_project.getCompileSourceRoots().iterator(); i.hasNext(); ) {
builder.addSourceTree(new File((String) i.next()));
}
// scan local source for javadoc tags
JavaSource[] javaSources = builder.getSources();
for (int i = 0; i < javaSources.length; i++) {
JavaClass mojoClass = javaSources[i].getClasses()[0];
// need plugin inheritance
DocletTag extendsTag = mojoClass.getTagByName(EXTENDS_PLUGIN);
if (null != extendsTag) {
String pluginName = extendsTag.getValue();
getLog().info("Extending " + pluginName + " plugin");
// lookup using simple plugin name (ie. compiler, archetype, etc.)
PluginXml superPlugin = getDependentPluginMetaData(pluginName);
if (null == superPlugin) {
throw new MojoExecutionException(pluginName + " plugin is not a dependency");
} else {
mergePluginMojo(mojoClass, targetPlugin, superPlugin);
}
}
}
try {
targetPlugin.write();
} catch (IOException e) {
throw new MojoExecutionException("cannot update local plugin metadata", e);
}
}
/**
* Loads plugin metadata for the given plugin location
*
* @param pluginDir root directory for the compiled plugin
* @return plugin metadata
* @throws MojoExecutionException
*/
private PluginXml loadPluginMetadata(File pluginDir)
throws MojoExecutionException {
File metadata = new File(pluginDir, "META-INF/maven/plugin.xml");
try {
return new PluginXml(metadata);
} catch (IOException e) {
throw new MojoExecutionException("cannot read plugin metadata " + metadata, e);
} catch (XmlPullParserException e) {
throw new MojoExecutionException("cannot parse plugin metadata " + metadata, e);
}
}
/**
* Loads plugin metadata for specified plugin found in this project's dependencies.
* <p/>
* -- Modified by Michael Zhou --
*
* @return mapping from simple plugin name to plugin metadata
* @throws MojoExecutionException
*/
private PluginXml getDependentPluginMetaData(String pluginName) throws MojoExecutionException {
File buildArea = new File(m_project.getBuild().getDirectory(), "plugins");
PluginXml pluginXml = null;
for (Iterator i = m_project.getDependencyArtifacts().iterator(); i.hasNext(); ) {
Artifact artifact = (Artifact) i.next();
if ("maven-plugin".equals(artifact.getType()) || "jar".equals(artifact.getType())) {
resolve(artifact);
if (artifact.getFile() == null) {
continue;
}
// extract simple plugin name by applying the standard maven naming rules in reverse
String name = artifact.getArtifactId().replaceAll("(?:maven-)?(\\w+)(?:-maven)?-plugin", "$1");
if (pluginName.equals(name) || pluginName.equals(artifact.getArtifactId())) {
File unpackDir = new File(buildArea, artifact.getArtifactId());
unpackPlugin(artifact, unpackDir);
pluginXml = loadPluginMetadata(unpackDir);
break;
}
}
}
return pluginXml;
}
private void resolve(Artifact artifact) {
try {
m_resolver.resolve(artifact, m_project.getRemoteArtifactRepositories(), m_localRepository);
} catch (ArtifactResolutionException e) {
return;
} catch (ArtifactNotFoundException e) {
return;
}
}
/**
* Unpacks a maven plugin artifact to the given directory
*
* @param artifact maven plugin
* @param unpackDir directory to unpack to
* @throws MojoExecutionException
*/
private void unpackPlugin(Artifact artifact, File unpackDir)
throws MojoExecutionException {
File pluginFile = artifact.getFile();
unpackDir.mkdirs();
try {
UnArchiver unArchiver = m_archiverManager.getUnArchiver(pluginFile);
unArchiver.setDestDirectory(unpackDir);
unArchiver.setSourceFile(pluginFile);
unArchiver.extract();
} catch (NoSuchArchiverException e) {
throw new MojoExecutionException("cannot find unarchiver for " + pluginFile, e);
} catch (IOException e) {
throw new MojoExecutionException("problem reading file " + pluginFile, e);
} catch (ArchiverException e) {
throw new MojoExecutionException("problem unpacking file " + pluginFile, e);
}
}
/**
* Inherits a mojo descriptor from a dependent plugin and merge it with the local plugin metadata
*
* @param mojoClass local mojo code requiring inheritance
* @param targetPlugin local plugin metadata
* @param superPlugin plugin metadata being extended
* @throws MojoExecutionException
*/
private void mergePluginMojo(JavaClass mojoClass, PluginXml targetPlugin, PluginXml superPlugin)
throws MojoExecutionException {
DocletTag goalTag = mojoClass.getTagByName(GOAL);
if (null == goalTag) {
return;
}
DocletTag superGoalTag = mojoClass.getTagByName(EXTENDS_GOAL);
if (null == superGoalTag) {
superGoalTag = goalTag;
}
String goal = goalTag.getValue();
String superGoal = superGoalTag.getValue();
getLog().info(superGoal + " => " + goal);
Xpp3Dom targetMojoXml = targetPlugin.findMojo(goal);
Xpp3Dom superMojoXml = superPlugin.findMojo(superGoal);
if (null == superMojoXml) {
throw new MojoExecutionException("cannot find " + superGoal + " goal in " + superPlugin);
}
PluginXml.mergeMojo(targetMojoXml, superMojoXml);
}
}